package com.mapforge.map;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;

/**
 * This class is a high level representation of a matrix of tiles in a tile map.
 * 
 * @author Will Morrison
 * 
 */
public class TileLayer implements Comparable<TileLayer> {
	/**
	 * Whether this layer is visible. Layers which are not visible will not be
	 * rendered
	 */
	private boolean visible;

	/**
	 * Indicates the opacity of this tile layer, 0 being entirely transparent
	 * and 1 being entirely opaque. Other values are invalid.
	 */
	private float opacity;

	/**
	 * The locked value of this layer
	 */
	private boolean locked;

	/**
	 * The z-depth of this layer
	 */
	private int z;
	private int x;
	private int y;

	/**
	 * The name of this layer
	 */
	private String name;

	/**
	 * The tiles which this layer represents
	 */
	private Tile[][] data;

	private Tileset tileset;

	public TileLayer(String name, Tileset tileset, int totalTilesWide,
			int totalTilesHigh) {
		visible = true;
		opacity = 1f;
		locked = false;
		x = y = z = 0;
		this.name = name;
		this.tileset = tileset;
		this.data = new Tile[totalTilesHigh][totalTilesWide];
		for (int y = 0; y < totalTilesHigh; y++) {
			for (int x = 0; x < totalTilesWide; x++) {
				this.data[y][x] = tileset.getEmptyTile();
			}
		}
	}

	/**
	 * If this layer isn't locked, this tile layer's data will be set to the
	 * specified value. Otherwise a TileLayerException will be thrown.
	 * 
	 * @param data
	 *            this layer's new data
	 */
	public void setData(Tile[][] data) {
		if (!isLocked()) {
			this.data = data;
		} else
			throw new TileLayerEditException(this);
	}

	/**
	 * If this layer isn't locked, this layer's data will be returned. Otherwise
	 * a TileLayerEditException will be thrown.
	 * 
	 * @return this layer's data
	 */
	public Tile[][] getData() {
		if (!isLocked())
			return data;
		else {
			throw new TileLayerEditException(this);
		}
	}

	public Tile getTileAt(int x, int y) {
		return data[y][x];
	}

	/**
	 * Sets the name of this tile layer.
	 * 
	 * @param name
	 *            this layer's new name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Returns the name of this tile layer.
	 * 
	 * @return this tile layer's name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Returns whether this layer is visible. Layers which aren't visible will
	 * not be rendered.
	 * 
	 * @return whether this layer is visible
	 */
	public boolean isVisible() {
		return visible;
	}

	/**
	 * Sets this layer's visibility. Layers which aren't visible will not be
	 * rendered
	 * 
	 * @param visible
	 *            this layer's new visibility
	 */
	public void setVisible(boolean visible) {
		this.visible = visible;
	}

	public float getOpacity() {
		return this.opacity;
	}

	public void setOpacity(float opacity) {
		if (opacity < 0 || opacity > 1) {
			throw new IllegalArgumentException(
					"Tile Layer opacity must lie between 0 and 1");
		}
		this.opacity = opacity;
	}

	/**
	 * Returns whether this layer is locked. Layers which are locked do not
	 * allow editing of their data.
	 * 
	 * @return whether this layer allows editing of its tiles
	 */
	public boolean isLocked() {
		return locked;
	}

	/**
	 * Sets whether this layer is locked. Layers which are locked do not allow
	 * editing of their data.
	 * 
	 * @param locked
	 *            this layer's new locked value.
	 */
	public void setLocked(boolean locked) {
		this.locked = locked;
	}

	/** */
	public void setTile(int x, int y, Tile tile) {
		data[y][x] = tile;
	}

	/** */
	public int getTotalHorizontalTiles() {
		return data.length > 0 ? data[0].length : 0;
	}

	/** */
	public int getTotalVerticalTiles() {
		return data.length;
	}

	public int compareTo(TileLayer layer) {
		if (z == layer.getZ())
			return 0;
		else if (z < layer.getZ())
			return -1;
		else
			return 1;
	}

	public void setX(int x) {
		this.x = x;
	}

	public void setY(int y) {
		this.y = y;
	}

	/**
	 * Sets this layer's z-depth to the specified value. The z-depth indicates
	 * how deep on the z-axis this layer should be rendered. Layers with higher
	 * z-depth values will be obscured by layers with lower values.
	 * 
	 * @param z
	 *            this layer's z-depth
	 */
	public void setZ(int z) {
		this.z = z;
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}

	/**
	 * Returns the z-depth of this tile layer. The z-depth indicates how deep on
	 * the z-axis this layer should be rendered. Layers with higher z-depth
	 * values will be obscured by layers with lower values.
	 * 
	 * @return this layer's z-depth
	 */
	public int getZ() {
		return z;
	}

	public void render(Graphics2D g) {
		render(g, x, y, 0, 0, data[0].length, data.length);
	}

	public void render(Graphics2D g, Point location, Rectangle tileClip) {
		render(g, location.x, location.y, tileClip.x, tileClip.y, tileClip.x
				+ tileClip.width, tileClip.y + tileClip.height);
	}

	/**
	 * Renders all tiles in this layer which fall within the specified clip in
	 * the supplied graphics context. The clips coordinates should reference the
	 * tile layer directly, (ie. a change of 1 unit equals a change of 1 tile.)
	 * 
	 * For example:
	 * 
	 * <pre>
	 * int x = 10, y = 10;
	 * int srcX = 0, srcRight = 10, srcY = 0, srcBottom = 10;
	 * tileLayer.render(g, x, y, srcX, srcY, srcRight, srcBottom);
	 * </pre>
	 * 
	 * This code will render all tiles falling within the specified area in the
	 * referenced tile layer at position (10, 10).
	 * 
	 * @param g
	 * @param x
	 * @param y
	 */
	public void render(Graphics2D g, int xloc, int yloc, int srcX, int srcY,
			int srcRight, int srcBottom) {
		if (isVisible()) {
			Tile[] row;
			int x = xloc;
			for (int y1 = srcY; y1 < srcBottom; y1++) {
				row = data[y1];
				xloc = x;
				for (int x1 = srcX; x1 < srcRight; x1++) {
					Tile tile = row[x1];
					if (tile != tileset.getEmptyTile()) {
						tile.render(g, xloc, yloc);
					}
					xloc += tileset.getTileWidth();
				}
				yloc += tileset.getTileHeight();
			}
		}
	}
}
